Skip to main content

Management API

TideCloak provides a Management API (Keycloak-compatible Admin REST) to manage realms, clients, users, roles, and more. It's ideal for automating provisioning, CI/CD, migrations, and back-office tasks.


1. Prerequisites​

  • A running TideCloak server (dev, self-hosted, or managed).
  • An admin user or a confidential client with service account credentials.
  • Network access to the admin endpoints over HTTPS.

Base URLs

  • Modern Keycloak/TideCloak (Quarkus): https://<host>/realms/<realm>/protocol/openid-connect/token Admin REST base: https://<host>/admin/realms/<realm>/...
  • Legacy path (older distros include /auth): https://<host>/auth/realms/<realm>/protocol/openid-connect/token Admin REST base: https://<host>/auth/admin/realms/<realm>/...

Use whichever matches your deployment. Your earlier console URLs should hint which style you're on.


2. Try the API via Swagger​

You can experiment with the API using the hosted Swagger UI: https://apidocs.tidecloak.com.

Prereqs

  • A realm (e.g., swagger-realm)
  • A client (e.g., swagger-ui)
  • A user with a linked Tide account

Steps

  1. Set Base URL to your TideCloak host.
  2. Enter the client_id (your client, not admin-cli unless that's what you use).
  3. Click Authorize and sign in with your Tide-linked account, or paste a Bearer token.

Create a confidential client with Service accounts enabled and grant it only the required realm-management roles (least privilege!):

  • Typical roles: view-users, manage-users, view-clients, manage-clients, view-realm, manage-realm (pick only what you need).

Token (Bash)

export HOST="https://<host>"
export REALM="master" # or the admin realm you use for tokens
export CLIENT_ID="admin-bot"
export CLIENT_SECRET="<client-secret>"
curl -sS -X POST "$HOST/realms/$REALM/protocol/openid-connect/token" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=client_credentials" -d "client_id=$CLIENT_ID" -d "client_secret=$CLIENT_SECRET"

4. Common Admin Tasks (cURL)​

Replace TOKEN with your access token, and ADMREALM with the realm you are administrating (often a non-master realm).

List users​

curl -sS "$HOST/admin/realms/ADMREALM/users?search=bob&first=0&max=20" -H "Authorization: Bearer $TOKEN"

Create a user​

curl -sS -X POST "$HOST/admin/realms/ADMREALM/users" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{
"username": "bob",
"email": "bob@example.com",
"enabled": true
}' -i
# On success returns 201 and a Location header with the new user ID.

Assign a realm role to a user​

# 1) Get the role representation
ROLE="admin"
ROLE_JSON=$(curl -sS "$HOST/admin/realms/ADMREALM/roles/$ROLE" -H "Authorization: Bearer $TOKEN")
# 2) Map it to the user
curl -sS -X POST "$HOST/admin/realms/ADMREALM/users/$USER_ID/role-mappings/realm" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d "[$ROLE_JSON]"

Create a client and fetch its secret​

# Create client
curl -sS -X POST "$HOST/admin/realms/ADMREALM/clients" -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{
"clientId": "myclient",
"publicClient": false,
"serviceAccountsEnabled": true,
"redirectUris": ["https://app.example.com/*"]
}' -i
# Find client ID by clientId
CID=$(curl -sS "$HOST/admin/realms/ADMREALM/clients?clientId=myclient" -H "Authorization: Bearer $TOKEN" | jq -r '.[0].id')
# Get client secret
curl -sS "$HOST/admin/realms/ADMREALM/clients/$CID/client-secret" -H "Authorization: Bearer $TOKEN"

5. C# HttpClient Examples​

Get token (Client Credentials)​

using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
var host = "https://<host>";
var realm = "master";
var clientId = "admin-bot";
var clientSecret = "<client-secret>";
using var http = new HttpClient();
var content = new FormUrlEncodedContent(new[] {
new KeyValuePair<string,string>("grant_type","client_credentials"),
new KeyValuePair<string,string>("client_id",clientId),
new KeyValuePair<string,string>("client_secret",clientSecret),
});
var tokenRes = await http.PostAsync($"{host}/realms/{realm}/protocol/openid-connect/token", content);
var tokenJson = await tokenRes.Content.ReadAsStringAsync();
// parse access_token and use below

List users​

http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var res = await http.GetAsync($"{host}/admin/realms/ADMREALM/users?first=0&max=20");
var body = await res.Content.ReadAsStringAsync();


6. Error Handling Cheat-sheet​

  • 401 Unauthorized: Missing/expired token.
  • 403 Forbidden: Token lacks required realm-management permissions.
  • 404 Not Found: Wrong ID/realm or insufficient privileges hide resources.
  • 409 Conflict: Duplicate name/constraint violation (e.g., username exists).
  • 400 Bad Request: Invalid payload or parameters.

Log the response body; admin endpoints usually return a useful message.


7. Security & Best Practices​

  • Prefer Client Credentials with a minimal-scope service account over password grant.
  • Assign only needed realm-management roles (least privilege).
  • Use HTTPS, rotate secrets, and store them in a secure secret manager.
  • Do not expose admin tokens client-side.
  • For production changes requiring governance, document how they're approved in your process (see the QEA (Quorum Enforced Authorization) / Change Requests section below).

8. QEA / Change Requests​

With QEA on, a governed admin write returns 202 Accepted with a change request (CR) id instead of applying. Authorize records one approval and auto-commits at quorum; Commit applies an already-approved CR (412 QUORUM_NOT_MET below quorum).

Endpoints: /admin/realms/{realm}/iga/.... The master realm is exempt.

For a complete, code-grounded description of the Tide-specific admin endpoints (vendor setup, licensing, the full IGA change-request lifecycle, Ragnarok offboarding, and the public console config) that the tide-console uses, see the Admin API Reference.

8.1 A governed write​

curl -i -X POST "$HOST/admin/realms/ADMREALM/users/$USER_ID/role-mappings/realm" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d "[$ROLE_JSON]"
# HTTP/1.1 202 Accepted
# Location: /admin/realms/ADMREALM/iga/change-requests/<CR_ID>
# { "status":"PENDING", "changeRequestId":"<CR_ID>",
# "entityType":"USER", "actionType":"GRANT_ROLES", "message":"..." }

202 means "captured for approval", not "done". The Location header points at the CR.

8.2 Change-request lifecycle endpoints​

Require an admin token with manage-realm (read-only listings: view-realm).

Method and pathPurposeNotes
GET /iga/change-requests?status=PENDINGList CRs (defaults to PENDING).Each item carries threshold, authorizationCount, readyToCommit, requiredApproverRoles, scopeMode.
GET /iga/change-requests/{id}Fetch a single CR.404 if unknown or in another realm.
POST /iga/change-requests/{id}/authorizeRecord one approval by the calling admin. Auto-commits when this approval reaches quorum.200 on success. 403 (missing approver role), 409 (not PENDING, or already signed by this admin).
POST /iga/change-requests/{id}/commitApply a CR that has already reached quorum (replay and apply).200 (now APPROVED), 403 (missing role), 412 (QUORUM_NOT_MET when below quorum, or an unmet dependency), 404 (ENTITY_VANISHED).
POST /iga/change-requests/{id}/denyDeny a CR.204 No Content.
POST /iga/change-requests/bulk-authorizeDrain many CRs in one call (typically the toggle-on ADOPT set).200 even when individual CRs reject; read the per-CR results array. 429 if another bulk run holds the per-realm lock.
PUT /iga/change-requests/{id}Replace the CR rows; wipes all authorizations (re-signing required).Body {"rows":[...]}.

End-to-end (capture, then authorize until quorum):

# 1. The governed write returned 202 with <CR_ID> (see 8.1).
# 2. Authorize with distinct admins. The approval that reaches quorum
# auto-commits, so the change applies and the CR becomes APPROVED.
curl -s -X POST "$HOST/admin/realms/ADMREALM/iga/change-requests/$CR_ID/authorize" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" -d '{}'
# 3. (Only if needed) Commit applies a CR that already reached quorum.
# Below quorum it returns 412 QUORUM_NOT_MET.
curl -s -X POST "$HOST/admin/realms/ADMREALM/iga/change-requests/$CR_ID/commit" \
-H "Authorization: Bearer $TOKEN"

8.3 QEA response-code reference​

StatusMeaning
202 AcceptedA governed admin write was captured into a CR. Body {status:"PENDING", changeRequestId, entityType, actionType, message}; Location points at the CR.
200 OKList / get, or an authorize / commit that succeeded.
204 No ContentDeny, or a policy delete.
403 ForbiddenMissing approver role on authorize / commit.
404 Not FoundUnknown CR id; also ENTITY_VANISHED on an ADOPT commit whose target was deleted out of band.
409 ConflictCR not PENDING, the same admin already signed it, or a conflicting CR is already pending for the entity.
412 Precondition FailedCommit gate not met: below quorum (QUORUM_NOT_MET) or an unmet dependency.
429 Too Many RequestsA bulk-authorize is already running for this realm.

9. Troubleshooting​

  • Token URL mismatch: If you see 404s on /realms/.../protocol/openid-connect/token, try the legacy /auth/realms/... path (and vice-versa).
  • CORS: Admin REST is normally for server-to-server; use it from backend services to avoid CORS.
  • Swagger auth: If "Authorize" fails, verify client type, redirect URIs, and whether the client is public vs confidential.